package service

import (
	"chat/models"
	"chat/utils"
	"encoding/json"
	"fmt"
	"golang.org/x/net/context"
	"golang.org/x/net/websocket"
	"strconv"
	"sync"
	"time"
)

// 维护在线用户连接的映射，key为会话标识(fromId-targetId的有序组合)
var (
	clientConnections = make(map[string][]*websocket.Conn)
	connMutex         = sync.RWMutex{}
)

// 初始化Redis订阅器
func init() {
	go subscribeToConversationChannels()
}

// 生成会话标识，确保fromId和targetId顺序一致
func getConversationKey(fromId, targetId uint) string {
	// 排序ID，确保A-B和B-A生成相同的会话标识
	if fromId > targetId {
		fromId, targetId = targetId, fromId
	}
	return fmt.Sprintf("conversation:%d-%d", fromId, targetId)
}

func Chat(ws *websocket.Conn) {
	fmt.Println("开启长链接")
	defer func(ws *websocket.Conn) {
		err := ws.Close()
		if err != nil {
			fmt.Println("关闭长链接失败:", err)
		} else {
			fmt.Println("关闭长链接成功")
		}
	}(ws)

	req := ws.Request()
	// 获取登录用户信息
	tokenValue, err := utils.GetTokenValueByQuery(req)
	if err != nil {
		fmt.Println("登录过期:", err)
		return
	}
	// 获取发送者id
	fromId := tokenValue.UserId
	// 获取接收者id
	t, err := strconv.ParseUint(req.URL.Query().Get("targetId"), 10, 0)
	if err != nil {
		fmt.Println("获取接收者id失败:", err)
		return
	}
	targetId := uint(t)

	// 生成会话标识
	conversationKey := getConversationKey(fromId, targetId)

	// 将连接添加到会话连接映射
	connMutex.Lock()
	clientConnections[conversationKey] = append(clientConnections[conversationKey], ws)
	connMutex.Unlock()
	defer func() {
		// 从连接映射中移除当前连接
		connMutex.Lock()
		defer connMutex.Unlock()
		if conns, ok := clientConnections[conversationKey]; ok {
			for i, conn := range conns {
				if conn == ws {
					// 移除当前连接
					clientConnections[conversationKey] = append(conns[:i], conns[i+1:]...)
					// 如果没有连接了就删除这个会话键
					if len(clientConnections[conversationKey]) == 0 {
						delete(clientConnections, conversationKey)
					}
					break
				}
			}
		}
	}()

	// 向客户端发送初始消息列表
	messageList := models.GetMessageList(fromId, targetId)
	messageListStr, err := json.Marshal(models.MessageListToVo(messageList))
	if err != nil {
		fmt.Println("json转换失败:", err)
		return
	}
	err = websocket.Message.Send(ws, messageListStr)
	if err != nil {
		fmt.Println("发送数据失败:", err)
		return
	}

	for {
		var content string
		err := websocket.Message.Receive(ws, &content)
		if err != nil {
			fmt.Println("读取客户端数据失败:", err)
			return
		}

		// 处理消息并保存
		message := models.Message{
			FromId:   fromId,
			TargetId: targetId,
			Content:  content,
		}
		savedMessage := models.SendMessage(message)

		// 转换为VO并发布到Redis
		messageList := []models.Message{savedMessage}
		messageListStr, err := json.Marshal(models.MessageListToVo(messageList))
		if err != nil {
			fmt.Println("json转换失败:", err)
			continue
		}

		// 发布消息到会话频道
		if err := publishToConversationChannel(fromId, targetId, messageListStr); err != nil {
			fmt.Printf("发布消息到会话 %d-%d 失败: %v\n", fromId, targetId, err)
		}
	}
}

// 向会话的所有连接广播消息
func broadcastToConversation(conversationKey string, messageListStr []byte) error {
	connMutex.RLock()
	conns, exists := clientConnections[conversationKey]
	connMutex.RUnlock()

	if !exists || len(conns) == 0 {
		return nil
	}

	// 向该会话的所有连接发送消息
	for _, ws := range conns {
		// 单独处理每个连接的发送，避免一个连接出错影响其他连接
		go func(ws *websocket.Conn) {
			if err := websocket.Message.Send(ws, messageListStr); err != nil {
				fmt.Printf("向会话 %s 的连接发送消息失败: %v\n", conversationKey, err)
			}
		}(ws)
	}
	return nil
}

// 订阅所有会话的消息频道
func subscribeToConversationChannels() {
	// 等待Redis连接初始化完成
	for {
		if utils.RDB != nil {
			break
		}
		time.Sleep(500 * time.Millisecond)
	}

	// 订阅所有会话频道，格式为 "conversation:{id1}-{id2}"
	pubsub := utils.RDB.PSubscribe(context.Background(), "conversation:*")
	defer pubsub.Close()

	for {
		msg, err := pubsub.ReceiveMessage(context.Background())
		if err != nil {
			fmt.Printf("Redis订阅错误: %v\n", err)
			continue
		}

		// 向会话中的所有连接广播消息
		broadcastToConversation(msg.Channel, []byte(msg.Payload))
	}
}

// 向Redis发布消息到指定会话频道
func publishToConversationChannel(fromId, targetId uint, message []byte) error {
	channel := getConversationKey(fromId, targetId)
	return utils.RDB.Publish(context.Background(), channel, message).Err()
}
